/**
 * 高性能ECEF坐标转换库（基于简化球模型）
 * 支持JWH(经纬度高程)与XYZ(ECEF)坐标双向转换
 * 提供单个坐标转换和批量Float64Array转换接口
 */

// ==========================
// 类型定义
// ==========================

/**
 * 大地坐标接口（经纬度/高程）
 * @property longitude 经度（°，范围：-180~180，东为正）
 * @property latitude 纬度（°，范围：-90~90，北为正）
 * @property height 高程（m，球面以上为正）
 */
export interface JWH {
  longitude: number;
  latitude: number;
  height: number;
}

/**
* XYZ坐标接口（对应ECEF坐标系）
* @property x X轴坐标（m，指向本初子午线-赤道交点）
* @property y Y轴坐标（m，赤道平面内逆时针90°）
* @property z Z轴坐标（m，指向地球北极）
*/
export interface XYZ {
  x: number;
  y: number;
  z: number;
}

/**
* 球体半径预设（单位：米）
*/
export enum SphereRadius {
  WGS84_SEMIMAJOR = 6378137,      // WGS84长半轴（默认）
  WGS84_AVERAGE = 6371008,        // WGS84平均半径
  EARTH_MEAN = 6371000,           // 地球平均半径（近似值）
}

// ==========================
// 预计算常量（避免重复计算）
// ==========================
const PI = Math.PI;
const PI_OVER_180 = PI / 180;
const ONE_EIGHTY_OVER_PI = 180 / PI;

// ==========================
// 核心转换函数（单个坐标）
// ==========================

/**
* 大地坐标(度)转XYZ坐标（ECEF）- 单个坐标对象
* @param jwh 大地坐标对象
* @param radius 球体半径（默认：WGS84长半轴）
* @returns XYZ坐标对象
*/
export function fromDegree(jwh: JWH, radius: number = SphereRadius.WGS84_SEMIMAJOR): XYZ {
  const lonRad = jwh.longitude * PI_OVER_180;
  const latRad = jwh.latitude * PI_OVER_180;

  const cosLat = Math.cos(latRad);
  const sinLat = Math.sin(latRad);
  const cosLon = Math.cos(lonRad);
  const sinLon = Math.sin(lonRad);

  const rPlusH = radius + jwh.height;
  const rCosLat = rPlusH * cosLat;

  return {
      x: rCosLat * cosLon,
      y: rCosLat * sinLon,
      z: rPlusH * sinLat
  };
}

/**
* XYZ坐标（ECEF）转大地坐标(度) - 单个坐标对象
* @param xyz XYZ坐标对象
* @param radius 球体半径（默认：WGS84长半轴）
* @returns 大地坐标对象
*/
export function toDegree(xyz: XYZ, radius: number = SphereRadius.WGS84_SEMIMAJOR): JWH {
  const x2 = xyz.x * xyz.x;
  const y2 = xyz.y * xyz.y;
  const z2 = xyz.z * xyz.z;

  const r = Math.sqrt(x2 + y2 + z2);
  const height = r - radius;

  let longitude = Math.atan2(xyz.y, xyz.x) * ONE_EIGHTY_OVER_PI;
  const latitude = Math.atan2(xyz.z, Math.sqrt(x2 + y2)) * ONE_EIGHTY_OVER_PI;

  // 归一化经度到[-180, 180]
  longitude = longitude > 180 ? longitude - 360 : longitude < -180 ? longitude + 360 : longitude;

  return {
      longitude,
      latitude,
      height
  };
}
/**
 * 计算给定XYZ坐标的单位法向量
 * @param xyz 输入的ECEF坐标
 * @returns 单位法向量（归一化后的XYZ坐标）
 */
export function normal(xyz: XYZ): XYZ {
  const { x, y, z } = xyz;
  const r = Math.sqrt(x * x + y * y + z * z);

  if (r === 0) {
      throw new Error('Cannot calculate normal vector for zero-length vector');
  }

  return {
      x: x / r,
      y: y / r,
      z: z / r
  };
}

/**
* 基于大地坐标（经纬度/高程）直接计算单位法向量
* @param jwh 大地坐标对象
* @returns 单位法向量（归一化后的XYZ坐标）
*/
export function normalFromDegree(jwh: JWH): XYZ {
  const lonRad = jwh.longitude * PI_OVER_180;
  const latRad = jwh.latitude * PI_OVER_180;

  const cosLat = Math.cos(latRad);
  const sinLat = Math.sin(latRad);
  const cosLon = Math.cos(lonRad);
  const sinLon = Math.sin(lonRad);

  return {
      x: cosLat * cosLon,
      y: cosLat * sinLon,
      z: sinLat
  };
}
// ==========================
// 批量单位法向量计算函数
// ==========================

/**
 * 批量计算给定XYZ坐标的单位法向量
 * @param xyzArray 输入数组，格式：[x0, y0, z0, x1, y1, z1, ...]
 * @returns 输出数组，格式：[nx0, ny0, nz0, nx1, ny1, nz1, ...]（单位法向量）
 */
export function normals(xyzArray: Float64Array): Float64Array {
  if (xyzArray.length % 3 !== 0) {
      throw new Error('Input array length must be a multiple of 3');
  }

  const result = new Float64Array(xyzArray.length);

  for (let i = 0; i < xyzArray.length; i += 3) {
      const x = xyzArray[i];
      const y = xyzArray[i + 1];
      const z = xyzArray[i + 2];

      const r = Math.sqrt(x * x + y * y + z * z);

      if (r === 0) {
          throw new Error('Cannot calculate normal vector for zero-length vector');
      }

      result[i] = x / r;
      result[i + 1] = y / r;
      result[i + 2] = z / r;
  }

  return result;
}

/**
* 批量基于大地坐标（经纬度/高程）直接计算单位法向量
* @param jwhArray 输入数组，格式：[longitude0, latitude0, height0, longitude1, latitude1, height1, ...]
* @returns 输出数组，格式：[nx0, ny0, nz0, nx1, ny1, nz1, ...]（单位法向量）
*/
export function normalFromDegrees(jwhArray: Float64Array): Float64Array {
  if (jwhArray.length % 3 !== 0) {
      throw new Error('Input array length must be a multiple of 3');
  }

  const result = new Float64Array(jwhArray.length);

  for (let i = 0; i < jwhArray.length; i += 3) {
      const lonRad = jwhArray[i] * PI_OVER_180;
      const latRad = jwhArray[i + 1] * PI_OVER_180;

      const cosLat = Math.cos(latRad);
      const sinLat = Math.sin(latRad);
      const cosLon = Math.cos(lonRad);
      const sinLon = Math.sin(lonRad);

      result[i] = cosLat * cosLon;
      result[i + 1] = cosLat * sinLon;
      result[i + 2] = sinLat;
  }

  return result;
}

// ==========================
// 核心转换函数（批量处理）
// ==========================

/**
* 批量大地坐标(度)转XYZ坐标（ECEF）- Float64Array版本
* @param jwhArray 输入数组，格式：[longitude0, latitude0, height0, longitude1, latitude1, height1, ...]
* @param radius 球体半径（默认：WGS84长半轴）
* @returns 输出数组，格式：[x0, y0, z0, x1, y1, z1, ...]
*/
export function fromDegrees(
  jwhArray: Float64Array,
  radius: number = SphereRadius.WGS84_SEMIMAJOR
): Float64Array {
  if (jwhArray.length % 3 !== 0) {
      throw new Error('Input array length must be a multiple of 3');
  }

  const result = new Float64Array(jwhArray.length);

  for (let i = 0; i < jwhArray.length; i += 3) {
      const longitude = jwhArray[i];
      const latitude = jwhArray[i + 1];
      const height = jwhArray[i + 2];

      const lonRad = longitude * PI_OVER_180;
      const latRad = latitude * PI_OVER_180;

      const cosLat = Math.cos(latRad);
      const sinLat = Math.sin(latRad);
      const cosLon = Math.cos(lonRad);
      const sinLon = Math.sin(lonRad);

      const rPlusH = radius + height;
      const rCosLat = rPlusH * cosLat;

      result[i] = rCosLat * cosLon;
      result[i + 1] = rCosLat * sinLon;
      result[i + 2] = rPlusH * sinLat;
  }

  return result;
}

/**
* 批量XYZ坐标（ECEF）转大地坐标(度) - Float64Array版本
* @param xyzArray 输入数组，格式：[x0, y0, z0, x1, y1, z1, ...]
* @param radius 球体半径（默认：WGS84长半轴）
* @returns 输出数组，格式：[longitude0, latitude0, height0, longitude1, latitude1, height1, ...]
*/
export function toDegrees(
  xyzArray: Float64Array,
  radius: number = SphereRadius.WGS84_SEMIMAJOR
): Float64Array {
  if (xyzArray.length % 3 !== 0) {
      throw new Error('Input array length must be a multiple of 3');
  }

  const result = new Float64Array(xyzArray.length);

  for (let i = 0; i < xyzArray.length; i += 3) {
      const x = xyzArray[i];
      const y = xyzArray[i + 1];
      const z = xyzArray[i + 2];

      const x2 = x * x;
      const y2 = y * y;
      const z2 = z * z;

      const r = Math.sqrt(x2 + y2 + z2);
      const height = r - radius;

      let longitude = Math.atan2(y, x) * ONE_EIGHTY_OVER_PI;
      const latitude = Math.atan2(z, Math.sqrt(x2 + y2)) * ONE_EIGHTY_OVER_PI;

      // 归一化经度到[-180, 180]
      longitude = longitude > 180 ? longitude - 360 : longitude < -180 ? longitude + 360 : longitude;

      result[i] = longitude;
      result[i + 1] = latitude;
      result[i + 2] = height;
  }

  return result;
}

// ==========================
// 辅助计算函数
// ==========================

/**
* 计算两个XYZ坐标之间的直线距离
* @param a 第一个XYZ坐标
* @param b 第二个XYZ坐标
* @returns 距离（米）
*/
export function distance(a: XYZ, b: XYZ): number {
  const dx = b.x - a.x;
  const dy = b.y - a.y;
  const dz = b.z - a.z;
  return Math.sqrt(dx * dx + dy * dy + dz * dz);
}

/**
* 计算多个XYZ坐标的中心坐标
* @param points XYZ坐标数组
* @returns 中心坐标
*/
export function center(points: XYZ[]): XYZ {
  let x = 0, y = 0, z = 0;
  const len = points.length;

  for (let i = 0; i < len; i++) {
      x += points[i].x;
      y += points[i].y;
      z += points[i].z;
  }

  return {
      x: x / len,
      y: y / len,
      z: z / len
  };
}

/**
* 计算两个Float64Array存储的XYZ坐标之间的距离
* @param a 第一个坐标 [x, y, z]
* @param b 第二个坐标 [x, y, z]
* @returns 距离（米）
*/
export function distanceArray(a: Float64Array, b: Float64Array): number {
  if (a.length < 3 || b.length < 3) {
      throw new Error('Input arrays must contain at least 3 elements');
  }
  const dx = b[0] - a[0];
  const dy = b[1] - a[1];
  const dz = b[2] - a[2];
  return Math.sqrt(dx * dx + dy * dy + dz * dz);
}

// ==========================
// 类型检查工具
// ==========================

/**
* 检查是否为有效的大地坐标(度)
* @param value 待检查值
* @returns 是否为有效大地坐标
*/
export function isJWH(value: unknown): value is JWH {
  if (typeof value !== 'object' || value === null) return false;
  const coords = value as JWH;
  return (
      typeof coords.longitude === 'number' && !isNaN(coords.longitude) &&
      typeof coords.latitude === 'number' && !isNaN(coords.latitude) &&
      typeof coords.height === 'number' && !isNaN(coords.height) &&
      coords.longitude >= -180 && coords.longitude <= 180 &&
      coords.latitude >= -90 && coords.latitude <= 90
  );
}

/**
* 检查是否为有效的XYZ坐标
* @param value 待检查值
* @returns 是否为有效XYZ坐标
*/
export function isXYZ(value: unknown): value is XYZ {
  if (typeof value !== 'object' || value === null) return false;
  const xyz = value as XYZ;
  return (
      typeof xyz.x === 'number' && !isNaN(xyz.x) &&
      typeof xyz.y === 'number' && !isNaN(xyz.y) &&
      typeof xyz.z === 'number' && !isNaN(xyz.z)
  );
}